#include "stdafx.h"
#include "ThreadHeartBeat.h"
#include "DateTime.h"
#include "IOCompletionPort.h"
#include "DataDefinition.h"
#include "MemoryPool.h"
#include <map>

ThreadHeartBeat::ThreadHeartBeat(ConnectionInformation* p_pclsClients, map<int,CircleDequeueBase*> p_mapCircleQueue)
	:m_pclsClients(p_pclsClients)
	,m_mapCircleQueue(p_mapCircleQueue)
	,m_uiCurrentID(TEST_BEGIN_ID)
{
}

void ThreadHeartBeat::Run()
{
	DateTime clsCurrentTime;
	DateTime clsLastDate;
	DateTime clsCurrentDate;
	clsLastDate.SetToCurrentDate();

	while(1)
	{
		// Get local time
		 clsCurrentTime.SetToCurrentDateTime();
		 clsCurrentDate.SetToCurrentDate();
		 if ((clsCurrentDate - clsLastDate) > 0)
		 {
			 LOG(INFO) << "Date changed from " << clsLastDate.DateToString() << " to " << clsCurrentDate.DateToString();
			 clsLastDate = clsCurrentDate;
			 CleanUpServer();
		 }

		// Loop to verify if clients are timeout
		 map<SOCKET,Connection> mapClients = m_pclsClients->GetConnectionList();
		 if (true == mapClients.empty())
		 {
			 Sleep(5000);
			 continue;
		 }

		 map<SOCKET,Connection>::iterator it = mapClients.begin();
		 while (it != mapClients.end())
		 {
			 unsigned int uiSpareTime = clsCurrentTime - it->second.m_cLastSendTime;
			 if (TIME_LIMIT < uiSpareTime)
			 {
				 if (TIMEOUT_LIMIT > uiSpareTime)
				 {
					 if (false == SendTestPacket(it->first))
					 {
						 // Close socket and remove client from client list and circle queue
						 LOG(WARNING) << "Cannot send test packet. Close client";
						 RemoveClient(it->first);
					 }
				 }
				 else
				 {
					 // Close socket and remove client from client list and circle queue
					 LOG(WARNING) << "Spare time greater than TIMEOUT_LIMIT value : " << (float)TIMEOUT_LIMIT/3600.00 << " hour. Close client";
					 RemoveClient(it->first);
				 }
			 }
			 it++;
		 }

		Sleep(5000);
	}
}

void ThreadHeartBeat::CleanUpServer()
{
	LOG(INFO) << "Start to clean up server";
	vector<SOCKET> vecClientSocket;
	map<SOCKET,Connection> mapClientList =  m_pclsClients->GetConnectionList();
	map<SOCKET,Connection>::iterator it = mapClientList.begin();
	while (it != mapClientList.end())
	{
		vecClientSocket.push_back(it->first);
		it++;
	}

	map<int,CircleDequeueBase*>::iterator itCircleQueue = m_mapCircleQueue.begin();
	while(itCircleQueue != m_mapCircleQueue.end())
	{
		itCircleQueue->second->ClearSocket(vecClientSocket);
		itCircleQueue++;
	}

	LOG(INFO) << "End to clean up server";
}

bool ThreadHeartBeat::SendTestPacket(SOCKET p_iClient)
{
	MemoryPool* pcMemoryPool = MemoryPool::Instance();
	while (false == pcMemoryPool->IsIDAvailable(m_uiCurrentID) && m_uiCurrentID < TEST_END_ID)
	{
		m_uiCurrentID++;
	}
	if (m_uiCurrentID >= TEST_END_ID)
	{
		LOG(ERROR) << "Test ID overflow";
		return false;
	}

	// Send receive buffer to IOCP
	char* pszReceiveBuffer = (char*)malloc(sizeof(DataHead));
	if(NULL == pszReceiveBuffer)
	{
		// Close socket
		LOG(ERROR) << "Cannot allocate memory for receive buffer";
		return false;
	}

	PER_IO_CONTEXT* pstReceiveContext = this->GetNewIOConetext(pszReceiveBuffer,sizeof(DataHead),RECV_POSTED,p_iClient,true);
	if(false == IOCompletionPort::Instance()->PostRecv(pstReceiveContext))
	{
		LOG(ERROR) << "Post receive error";
		free(pszReceiveBuffer);
		RELEASE(pstReceiveContext)
		return false;
	}

	// WSASend successful message
	char* pszSendBuffer = (char*)malloc(sizeof(DataHead));
	if(NULL == pszSendBuffer)
	{
		LOG(ERROR) << "Cannot allocate memory for send buffer";
		return false;
	}

	// Copy message to buffer
	DataHead stDataHead;
	stDataHead.m_ucDataType = 0;
	stDataHead.m_uiDataLength =0;
	memcpy(pszSendBuffer,&stDataHead,sizeof(DataHead));
	PER_IO_CONTEXT* pstSendContext = this->GetNewIOConetext(pszSendBuffer,sizeof(DataHead),TEST_POSTED,p_iClient,true);

	// Send content to client
	if(false == IOCompletionPort::Instance()->PostSend(pstSendContext))
	{
		free(pszSendBuffer);
		RELEASE(pstSendContext)
		LOG(ERROR) << "Post send error";
		return false;
	}

	LOG(INFO) << "Sent test packet to client " << p_iClient;
	return true;
}

void ThreadHeartBeat::RemoveClient(SOCKET p_iClient)
{
	// Remove client from circle queue
	map<int,CircleDequeueBase*>::iterator itCircleQueue = m_mapCircleQueue.begin();
	while(itCircleQueue != m_mapCircleQueue.end())
	{
		itCircleQueue->second->DeleteSocket(p_iClient);
		itCircleQueue++;
	}

	// Remove client from client information
	m_pclsClients->DeleteClient(p_iClient);

	// Close socket
	RELEASE_SOCKET(p_iClient)
}

char* ThreadHeartBeat::GetBufferFromMemoryPool(unsigned int p_uilength, unsigned int p_uiIndex)
{
	MemoryPool* pcMemoryPool = MemoryPool::Instance();

	// Verify parameters
	if(NULL == pcMemoryPool)
	{
		LOG(ERROR) << "Instance of memory pool is NULL";
		return NULL;
	}
	if(0 == p_uilength)
	{
		LOG(ERROR) << "Buffer length is 0";
		return NULL;
	}

	// Allocate buffer from memory pool
	return pcMemoryPool->Allocate(p_uilength, p_uiIndex);
}

PER_IO_CONTEXT* ThreadHeartBeat::GetNewIOConetext(char* p_szBuffer, unsigned int p_uiBufferLength, OPERATION_TYPE p_eType, SOCKET p_stSocket, bool p_bNeedRelease)
{
	// Verify parameters
	if(NULL == p_szBuffer)
	{
		LOG(ERROR) << "Parameter p_szBuffer is NULL";
		return NULL;
	}
	if(0 == p_uiBufferLength)
	{
		LOG(ERROR) << "Parameter p_uiBufferLength is 0";
		return NULL;
	}

	// New PER_IO_CONTEXT and assign value
	PER_IO_CONTEXT* pstIOContext = new PER_IO_CONTEXT();
	if(NULL == pstIOContext)
	{
		LOG(ERROR) << "Allocate IO context fail";
	}

	pstIOContext->m_wsaBuf.buf = p_szBuffer;
	pstIOContext->m_wsaBuf.len = p_uiBufferLength;
	pstIOContext->m_OpType = p_eType;
	pstIOContext->m_sockAccept = p_stSocket;
	pstIOContext->m_bNeedRelease = p_bNeedRelease;

	return pstIOContext;
}